/*
 *   Copyright 2023 <a href="mailto:asialjim@hotmail.com">Asial Jim</a>
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package com.asialjim.encommunicate.base.rsa;

import org.apache.commons.io.IOUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Objects;

/**
 * 基于 RSA 的解密工具
 *
 * @author Copyright © <a href="mailto:asialjim@hotmail.com">Asial Jim</a>   Co., LTD
 * @version 1.0
 * @since 2023/2/24, &nbsp;&nbsp; <em>version:1.0</em>, &nbsp;&nbsp; <em>java version:17</em>
 */
@SuppressWarnings("unused")
public interface RSADecrypt {

    /**
     * 解密
     *
     * @param source {@link String 待解密内容}
     * @return {@link String 解密结果}
     * @since 2023/2/24
     */
    String decrypt(String source);

    /**
     * 解密
     *
     * @param source {@link InputStream 待解密内容}
     * @return {@link java.lang.reflect.Array 解密结果}
     * @since 2023/2/24
     */
    byte[] decrypt(InputStream source);

    /**
     * 解密
     *
     * @param source {@link java.lang.reflect.Array 待解密内容}
     * @return {@link java.lang.reflect.Array 解密结果}
     * @since 2023/2/24
     */
    byte[] decrypt(byte[] source);

    default String decrypt(String source, String priKey) {
        byte[] decrypt = decrypt(Base64.getDecoder().decode(source), priKey);
        return new String(decrypt);
    }

    default byte[] decrypt(InputStream inputStream, String priKey) {
        ByteArrayOutputStream bos = null;
        try {
            byte[] priKeyBytes = Base64.getDecoder().decode(priKey);
            RSAPrivateKey privateKey = (RSAPrivateKey) KeyFactory.getInstance(RSAKeyPairProperty.keyAlgorithm()).generatePrivate(new PKCS8EncodedKeySpec(priKeyBytes));
            int maxBlockSize = privateKey.getModulus().bitLength() >> 3;
            if (inputStream.available() <= maxBlockSize)
                return decrypt(IOUtils.toByteArray(inputStream), priKey);

            Cipher cipher = Cipher.getInstance(RSAKeyPairProperty.keyAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            bos = new ByteArrayOutputStream();
            byte[] block;
            do {
                block = inputStream.readNBytes(maxBlockSize);
                if (Objects.isNull(block) || block.length == 0) break;

                bos.write(cipher.doFinal(block));
            }  while (block.length >= maxBlockSize);

            return bos.toByteArray();
        } catch (IOException | NoSuchPaddingException | IllegalBlockSizeException | InvalidKeySpecException |
                 NoSuchAlgorithmException | BadPaddingException | InvalidKeyException e) {
            throw new RuntimeException(e);
        } finally {
            IOUtils.closeQuietly(bos);
        }
    }

    default byte[] decrypt(byte[] source, String priKey) {
        ByteArrayInputStream bis = null;
        ByteArrayOutputStream bos = null;

        try {
            byte[] priKeyBytes = Base64.getDecoder().decode(priKey);
            RSAPrivateKey privateKey = (RSAPrivateKey) KeyFactory.getInstance(RSAKeyPairProperty.keyAlgorithm()).generatePrivate(new PKCS8EncodedKeySpec(priKeyBytes));
            Cipher cipher = Cipher.getInstance(RSAKeyPairProperty.keyAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            int maxBlockSize = privateKey.getModulus().bitLength() >> 3;
            if (source.length <= maxBlockSize)
                return cipher.doFinal(source);

            bis = new ByteArrayInputStream(source);
            bos = new ByteArrayOutputStream();

            byte[] block;
            do {
                block = bis.readNBytes(maxBlockSize);
                if (Objects.isNull(block) || block.length == 0) break;

                bos.write(cipher.doFinal(block));
            } while (block.length >= maxBlockSize);

            return bos.toByteArray();
        } catch (NoSuchPaddingException | IllegalBlockSizeException | InvalidKeySpecException |
                 NoSuchAlgorithmException | BadPaddingException | InvalidKeyException | IOException e) {

            throw new RuntimeException(e);
        } finally {
            IOUtils.closeQuietly(bis);
            IOUtils.closeQuietly(bos);
        }
    }
}